
class Thing

  # javascript style in the house
  ::Pattern::Property.enable(self, :attribute, {
      :getter => lambda { |o, param|
        o.recover_state
        o.instance_variable_get('@'+param.to_s)
      },
      :setter => lambda { |o, param, v|
        o.recover_state
        o.instance_variable_set('@'+param.to_s, v)
      }
    })

  attr_accessor :parent_thing, :element

  include DataMapper::Resource
  property :id, Serial
  property :identity, String
  property :version, String
  property :kind, String
  property :previous_version, String
  property :changed_at, Time
  property :state, Text

  def root
    root = parent = self
    while parent = parent.parent_thing
      root = parent
    end
    root
  end

  def can_move *a
    if a.length == 0
      @can_move != false
    else
      @can_move = a[0]
    end
  end



  def go e
    if e.data['json_event']
      begin
        if result = process_ajax_event(e.data['json_event'])
          e.data['json_event'].data['processed'] = true
          # this workaround was needed on linux
          data = result.map(&:to_hash).to_json
          e.respond({
              :response => Rack::Response.new(data)
          })
        end
      rescue Exception => e
        return e.to_s+"\n  "+e.backtrace.join("\n  ")
      end
    end
    wrap_render(e)
  end

  def process_ajax_event e
    method_name = "on_"+e.name
    if methods.include? method_name
      send(method_name, e)
      e.response
    else
      return false
    end
  end


  @@css_class_name ||= {}

  def css_class_name
    @@css_class_name[self] ||= self.class.to_s.gsub(/Thing$/, '')
  end
  
  def initialize(*a)
    super
    recover_state
    #set those up
    identity
    version
    previous_version
  end

  def each_thing &block
    accept_visitor &block
  end

  def accept_visitor &block
    block.call self
  end


  def unique_path
    @unique_path ||=
      if !new?
        'thing-'+self.class.to_s+'-'+id.to_s
      elsif parent = parent_thing
        parent.unique_path +
          '_' + self.class.kind + '-' +
            parent.children.index(self).to_s
      else
        'thing-'+self.class.to_s+'-new'
      end
  end

  
  def form_id (name = nil)
    unique_path + (name ? '|'+name.to_s : '')
  end


  def self.thumbnail
    self.to_s
  end

  def show_window yes
    @show_window = yes
    self
  end

  def creator_form e
    flash = ""
#    begin
      recieve e
#    rescue Exception => ex
#      flash = el(:div, ex.message, :class => 'flash flash-bad').to_s
#    end
    wrap_in_form(flash + (creator_window e))
  end

  def wrap_in_form content
    form = el(:form,
      :enctype => "multipart/form-data",
      :method => 'post'
    )
    form << '<input type="submit" class="form_submit" name="save" value="default_button" style="position:fixed; top:-40em;">'
    form << el(:input,
        :type => "hidden",
        :name => 'form_unique_path',
        :value => unique_path
      )
    form << content
    form << <<HTML
    <center>
      <input type="submit" class="form_submit" name="save" value="ulož">
      <input type="submit" class="form_submit" name="destroy" value="smaž" onclick="return confirm('Opravdu smazat?')">
    </center>
  </form>
HTML

    form
  end

  def buttons
    b = []
    if can_move && (parent_thing.is_a? ContainerThing)
    b.push(
      [:top,    { :value => "&uArr;" }],
      [:up,     { :value => "&uarr;" }],
      [:down,   { :value => "&darr;" }],
      [:bottom, { :value => "&dArr;" }]
    )
    end
    b << [:remove, { :value => "x" }]
    b
  end

  def header_label
    self.class.to_s
  end

  def creator_window e, around = nil
    around ||= creator(e).to_s
    return around if @show_window == false

    b = buttons

    (b.first[1][:classes] ||= []) << :first
    (b.last[1][:classes] ||= []) << :last

    id = form_id :action
    b = b.map {|definition|
      button = SubmitButton.new(
        :name => id+"[#{definition[0]}]",
        :class => (
          definition[1].delete(:classes) || []
        ).join(" ")
      )
      definition[1].each { |k, v|
        button.element.set_attribute_raw(k, v)
      }

      button
    }.join


    klass = "creatorWindow creatorWindow-#{self.class}"
    if (self.is_a? ContainerThing) && (children.length == 0)
      klass << " creatorWindow-empty"
    end

    <<HTML
  <div class="#{klass}">
    <div class="controls">
      <span class="label">#{self.header_label}</span>
      <span class="buttons">
        #{b}
      </span>
    </div>
    <div class="inner">
      #{around}
    </div>
  </div>
HTML
  end

  def widget
    @widget ||= Widget.new.tap { |w|
      w.set_primary_classes [css_class_name]
    }
  end

  def element
    widget.element
  end

  def wrap_render e    
    widget.element.children = [render e]
    if e.data['on'] && callback = e.on['thing_render']
      callback.call(self)
    end
    widget.to_s
  end

  def render e
    self.class.to_s
  end

  def get_data
    {}
  end

  def to_hash
    { 'thing' => self.class.to_s }.
      merge(Pattern::Property.data(self, 'attribute')).
      merge(get_data).
      merge(attributes).stringify_keys.tap { |h|
        h.delete 'state'
        h.delete 'id'
        h.delete 'kind'
      }
  end

  def self.from_json json
    from_hash JSON.parse(json)
  end

  def to_json
    to_hash.to_json
  end

  def recieve e
    if !parent_thing
      if e.params['destroy']
        destroy
      elsif e.request.post?
        save
      end
    else
      if action = e.params[(form_id :action)]
        action.each {|name, data|
          next if data == ''
          if name == 'bottom'
            parent_thing.move_bottom self
          end
          if name == 'up'
            parent_thing.move_up self
          end
          if name == 'down'
            parent_thing.move_down self
          end
          if name == 'top'
            parent_thing.move_top self
          end
          if name == 'remove'
            parent_thing.remove self
          end
        }
      end
    end
  end


  def fill_data data
    @state_recovered = true
    #subclasses override this
  end

  def recover_state
    if !state_recovered? && !new? && self.state
      recover_state_from_hash(JSON.parse self.state.to_s.dup)
    end
    self
  end

  def state_recovered?
    @state_recovered
  end

  def recover_state_from_hash state
    Pattern::Property.set_data(self, state)
    fill_data state
    @state_recovered = true
    self
  end

  def self.from_hash h, parent = nil
    raise "Expected hash, got #{h.inspect}" unless h.is_a? Hash
    h.stringify_keys!
    thing = to_const(h['thing']).new
    thing.parent_thing = parent
    thing.recover_state_from_hash(h)
    thing.class.send(:properties).each { |p|
      k = p.name.to_s
      if h.has_key? k
        thing.send(k+'=', h[k])
      end
    }
    thing
  end

    # Finds class according to its string name
  def self.to_const string, root_module = Module
    string = string.gsub('__','::')
    unless string.is_a?(String)
      raise "Class should be string. Was #{string.class}."
    end
    module_names = string.split('::')
    constant = root_module
    module_names.each do |name|
      constant = constant.const_get(name.to_sym)
    end
    constant
  end

  

  class SerializationTest < FwTest
    def test_to_hash
      t = Thing.new
      t.identity = "123"
      t.version = "456"
      t.previous_version = "789"

      h = t.to_hash
      h.delete :changed_at

      assert_equal h, {
        'thing' => 'Thing',
        'identity' => t.identity,
        'version' => t.version,
        'previous_version' => t.previous_version
      }
    end

    def test_from_hash
      assert_equal Thing.from_hash(Thing.new.to_hash).class, Thing
    end

    def test_recover_state_on_new_does_nething
      # no exception expected
      Thing.new.recover_state
      assert true
    end

    def test_from_hash_with_string_keys
      t = Thing.from_hash({
          'thing' => 'Thing',
          'identity' => '123',
          'version' => '456',
          'previous_version' => '789'
        })
      assert_equal t.identity, '123'
      assert_equal t.version, '456'
      assert_equal t.previous_version, '789'
    end

    def test_from_hash_with_symbol_keys
      t = Thing.from_hash({
          :thing => 'Thing',
          :identity => '123',
          :version => '456',
          :previous_version => '789'
        })
      assert_equal t.identity, '123'
      assert_equal t.version, '456'
      assert_equal t.previous_version, '789'
    end
  end


  def identity
    if !identity = attribute_get(:identity)
      return attribute_set(:identity, (VersionControl.random_token 32))
    end
    identity
  end

  def version
    if !version = attribute_get(:version)
      v = (VersionControl.random_token 16)
      self.previous_version = v
      return attribute_set(:version, v)
    end
    version
  end

  def previous_version
    if !previous_version = attribute_get(:previous_version)
      return attribute_set(:previous_version, version)
    end
    previous_version
  end


  def save new_version = true
    self.changed_at ||= Time.now
    if new_version && !new?
      self.changed_at = Time.now
      self.previous_version = @version
      self.version = VersionControl.random_token 16
    end
    #if new? || state_recovered?
      self.state = to_json
    #end
    self.kind = self.class.kind

    super() || (raise 'Could not save '+self.class.to_s+'.')
  end

  def self.kind
    self.to_s.gsub('::', '__')
  end

  def kind
    @kind || self.class.kind
  end

  def self.all(*o)
    if self != Thing
      (o[0] ||= {})[:kind] = kind
    end
    super *o
  end

  def self.first(*o)
    (o[0] ||= {})[:kind] = kind
    super *o
  end

  def self.last(*o)
    (o[0] ||= {})[:kind] = kind
    super *o
  end

  def self.get id
    if self == Thing
      to_const(all(:id => id).first.kind).get(id)
    else
      super
    end
  end

  def self.get! id
    if self == Thing
      to_const(all(:id => id).first.kind).get(id)
    else
      super
    end
  end


  
  # -------------------------------------------------------------

  class TestVersioning < FwTest
    def test_new_thing_gets_its_identity_assigned
      eq Thing.new.identity.length, 32
    end

    def test_identity_gets_persisted
      t = Thing.new
      DataMapper.setup(:test, :adapter => 'in_memory')
      repository(:test) {
        t.save
        eq Thing.first(:identity => t.identity).id, t.id
        eq Thing.first(:identity => t.identity).identity, t.identity
      }
    end

    def test_new_thing_gets_its_version_assigned
      eq Thing.new.version.length, 16
    end

    def test_version_gets_persisted
      t = Thing.new
      DataMapper.setup(:test, :adapter => 'in_memory')
      repository(:test) {
        t.save
        eq Thing.first(:version => t.version).id, t.id
        eq Thing.first(:version => t.version).version, t.version
      }
    end


    def test_previous_version_of_new_thing_is_the_version_itself
      t = Thing.new
      eq t.previous_version, t.version
    end

    def test_previous_version_gets_persisted
      t = Thing.new
      t.version = 'abc'
      t.previous_version = '123'
      DataMapper.setup(:test, :adapter => 'in_memory')
      repository(:test) {
        t.save
        eq Thing.first(:previous_version => t.previous_version).id, t.id
        eq Thing.first(:previous_version => t.previous_version).
          previous_version, t.previous_version
      }
    end


    def test_new_thing_gets_its_changed_at_assigned
      DataMapper.setup(:test, :adapter => 'in_memory')
      repository(:test) {
        t = Thing.new
        t.save
        eq t.changed_at.to_i, Time.now.to_i
      }
    end

    def test_version_gets_persisted
      t = Thing.new
      DataMapper.setup(:test, :adapter => 'in_memory')
      repository(:test) {
        t.save
        eq Thing.first(:changed_at => t.changed_at).id, t.id
        eq Thing.first(:changed_at => t.changed_at).changed_at, t.changed_at
      }
    end

    def test_recovering_does_not_override_attributes_not_included_in_hash
      t = Thing.new
      t.version = "abc"
      t.recover_state_from_hash :identity => '123'
      eq t.version, "abc"
    end
  end

  class TestPersistance < FwTest
    def setup
      DataMapper.setup(:test, :adapter => 'in_memory')
    end

    def test_can_save_blank
      repository(:test) {
        t = Thing.new
        assert t.save
      }
    end

    def test_saving_sets_kind
      repository(:test) {
        t = Thing.new
        assert t.save
        assert_equal t.kind, 'Thing'
      }
    end

    def test_saving_serializes_current_state
      repository(:test) {
        t = Thing.new
        assert t.save
        assert_equal t.state, t.to_hash.to_json
      }
    end

    class A < Thing; end
    class B < Thing; end
    class C < Thing; end

    def test_last_uses_type_to_query
      repository(:test) {
        A.new.save
        B.new.save
        C.new.save
        eq B.last.class, B
      }
    end

    def test_first_uses_type_to_query
      repository(:test) {
        A.new.save
        B.new.save
        C.new.save
        eq B.first.class, B
      }
    end

    def test_last_returns_last_record
      repository(:test) {
        a1 = A.new
        a1.save
        a2 = A.new
        a2.save
        eq A.last.id, a2.id
      }
    end

    def test_furst_returns_first_record
      repository(:test) {
        a1 = A.new
        a1.save
        a2 = A.new
        a2.save
        eq A.first.id, a1.id
      }
    end

    def test_all_selects_by_class_which_its_called_upon
      repository(:test) {
        A.new.save
        B.new.save
        C.new.save
        eq B.all[0].class, B
      }
    end

    def test_all_selects_everything_when_called_on_thing
      repository(:test) {
        A.new.save
        B.new.save
        C.new.save
        eq Thing.all.length, 3
      }
    end

    def test_get_returns_any_thing_by_that_id
      b = nil
      repository(:test) {
        a = A.new
        a.save
        b = B.new
        b.save
        c = C.new
        c.save
      }
      repository(:test) {
        eq B.get(b.id), b
      }
      repository(:test) {
        eq Thing.get(b.id), b
      }
    end
  end

  class PropertyPersistanceTest < FwTest

    class Picture < Thing
      attribute :size
    end

    def test_persist_one_attribute
      pm = Picture.new
      pm.size = [100,100]
      pm_hash = pm.to_hash

      pm2 = Thing.from_hash(pm_hash)

      assert_equal pm2.size, [100,100]
    end
  end

end